"""
OAuth 2.0 认证授权控制器
"""
import json
import secrets
from typing import Annotated

from fastapi import Depends, Query
from starlette.responses import RedirectResponse

from axiom_boot.api.controller import get, post
from axiom_boot.api.models import ApiResponse, success_response
from axiom_boot.auth.models import TokenRevokeRequest, TokenIssueRequestDTO, Principal
from axiom_boot.auth.security import get_current_principal_or_none
from axiom_boot.auth.services.client_verifier import ClientVerifier
from axiom_boot.auth.services.oauth_service import OAuthService, InvalidGrantError
from axiom_boot.cache.service import CacheService
from axiom_boot.di import autowired, controller, conditional_on_setting


@controller(prefix="/oauth", tags=["OAuth 2.0 认证授权"])
@conditional_on_setting(key="oauth_enabled")
@conditional_on_setting(key="database_enabled")
class TokenController:
    """
    遵循 OAuth 2.0 规范的认证授权端点。
    包括用于获取授权码的 /authorize 端点和用于交换令牌的 /token 端点。
    """
    oauth_service: OAuthService = autowired()
    cache_service: CacheService = autowired()
    client_verifier: ClientVerifier = autowired()

    def __init__(self):
        """
        空的构造函数，用于满足 DI 容器的实例化要求。
        所有依赖项均通过字段注入提供。
        """
        pass

    @get(
        "/authorize",
        summary="OAuth 2.0 授权端点",
        description="此端点是标准授权码流程的第一步，用于请求用户授权。成功后，将携带授权码重定向到客户端指定的回调URI。此端点不直接被API调用，而是由用户的浏览器访问。"
    )
    async def authorize(
        self,
        principal: Annotated[Principal, Depends(get_current_principal_or_none)],
        response_type: Annotated[str, Query(description="响应类型，对于授权码流程，此值必须为 'code'")],
        client_id: Annotated[str, Query(description="在系统中注册的客户端ID")],
        redirect_uri: Annotated[str, Query(description="客户端注册的、用于接收授权码的回调URI")],
        scope: Annotated[str, Query(description="客户端申请的权限范围，为多个权限代码（permission_code）以空格分隔的字符串")],
        state: Annotated[str | None, Query(description="客户端提供的随机字符串，用于防止CSRF攻击，服务器将在重定向时原样返回")] = None
    ):
        """
        根据 RFC 6749, Section 4.1.1 处理授权请求。
        1. 验证客户端与 `redirect_uri`。
        2. 检查用户是否已登录。
        3. 验证用户是否拥有客户端申请的全部权限 (`scope`)。
        4. 生成一次性授权码，存入缓存。
        5. 将用户重定向至 `redirect_uri`，并附上授权码和 `state`。
        """
        # 在标准的Web流程中，如果用户未登录(principal is None)，应重定向到登录页面。
        # 在此API优先的框架中，我们假定调用方（如测试脚本或已登录的前端）会确保用户已认证。
        # 此处抛出异常是为了明确指出必须先进行认证。
        if not principal:
            raise InvalidGrantError(message="用户未认证，无法进行授权")

        if response_type != 'code':
            # 根据规范，错误应重定向回客户端
            error_uri = f"{redirect_uri}?error=unsupported_response_type&error_description=response_type%20must%20be%20'code'"
            if state:
                error_uri += f"&state={state}"
            return RedirectResponse(url=error_uri)

        # 1. 验证客户端和回调地址
        await self.client_verifier.verify_by_id(client_id=client_id, redirect_uri=redirect_uri)

        # 2. 验证用户权限与申请的scope是否匹配
        requested_scopes = set(scope.split())
        user_permissions = set(principal.permissions)
        if not requested_scopes.issubset(user_permissions):
            error_uri = f"{redirect_uri}?error=access_denied&error_description=user%20does%20not%20have%20the%20required%20permissions"
            if state:
                error_uri += f"&state={state}"
            return RedirectResponse(url=error_uri)

        # 3. 生成授权码并缓存
        code = secrets.token_urlsafe(32)
        code_payload = {
            "user_id": principal.user_id,
            "client_id": client_id,
            "scope": scope,
            "redirect_uri": redirect_uri
        }
        await self.cache_service.set(
            f"oauth:code:{code}", 
            json.dumps(code_payload), 
            timeout=300
        ) # 授权码有效期5分钟

        # 4. 成功，重定向到客户端回调地址
        success_uri = f"{redirect_uri}?code={code}"
        if state:
            success_uri += f"&state={state}"
        
        return RedirectResponse(url=success_uri)

    @post(
        "/token",
        summary="OAuth 2.0 令牌端点",
        description=(
            "根据指定的授权类型 (grant_type) 颁发访问令牌和刷新令牌。\n\n"
            "支持的授权类型 (grant_type):\n"
            "- `authorization_code`: 使用从 /authorize 端点获取的授权码来交换令牌。\n"
            "- `password`: 使用用户的用户名和密码直接交换令牌（仅适用于高度信任的第一方客户端）。\n"
            "- `client_credentials`: 客户端使用自己的凭据获取令牌，与特定用户无关。\n"
            "- `refresh_token`: 使用刷新令牌获取新的访问令牌。"
        ),
        response_model=ApiResponse
    )
    async def issue_token(self, request: TokenIssueRequestDTO) -> ApiResponse:
        token_data = await self.oauth_service.issue_token(request)
        return success_response(data=token_data)

    @post(
        "/revoke",
        summary="OAuth 2.0 令牌吊销端点",
        description="根据 RFC 7009, 用于吊销一个刷新令牌或访问令牌，使其立即失效。",
        response_model=ApiResponse
    )
    async def revoke_token(self, request: TokenRevokeRequest) -> ApiResponse:
        """根据 RFC 7009, 吊销一个令牌。"""
        await self.oauth_service.revoke_token(
            token_str=request.token,
            token_type_hint=request.token_type_hint
        )
        return success_response(message="令牌已成功吊销")
